"""
提取HTML文件并转换为适合单表数据库的JSON格式
优化：移除不需要的字段，扁平化数据结构
"""
import json
import re
import sys
from pathlib import Path
from bs4 import BeautifulSoup
from typing import Dict, List, Optional


def extract_title_keywords(title: str) -> str:
    """
    从标题提取关键词
    提取：天干、地支、月份、理论关键词等
    """
    if not title:
        return ""
    
    keywords = []
    
    # 提取天干地支组合（如：甲子、乙丑）
    stem_branch = re.findall(r'([甲乙丙丁戊己庚辛壬癸][子丑寅卯辰巳午未申酉戌亥])', title)
    keywords.extend(stem_branch)
    
    # 提取月份（如：寅月、卯月）
    months = re.findall(r'([子丑寅卯辰巳午未申酉戌亥]月)', title)
    keywords.extend(months)
    
    # 提取理论关键词（如：论、取运）
    theory_keywords = re.findall(r'(论[^取]*|取运|格局)', title)
    keywords.extend(theory_keywords)
    
    # 提取日期组合（如：甲日、寅时）
    date_parts = re.findall(r'([甲乙丙丁戊己庚辛壬癸]日|[子丑寅卯辰巳午未申酉戌亥]时)', title)
    keywords.extend(date_parts)
    
    return ' '.join(keywords)


def extract_page_num(page_info: str) -> Optional[int]:
    """
    从page_info提取页码数字
    如：p001 -> 1, p123 -> 123
    """
    if not page_info:
        return None
    
    match = re.search(r'p(\d+)', page_info, re.IGNORECASE)
    if match:
        return int(match.group(1))
    return None


def format_title(title: str) -> Optional[str]:
    """
    格式化标题，去掉书籍前缀
    如：穷通宝鉴1-论甲生寅月 -> 论甲生寅月
    """
    if not title:
        return None
    
    # 匹配模式：书籍名 + 数字 + "-" 开头
    # 例如：穷通宝鉴1-、滴天髓1-、三命通会1-等
    pattern = r'^[^-]+?\d+-'
    formatted = re.sub(pattern, '', title)
    
    return formatted.strip()


def parse_text_bzcase_tags(text: str) -> tuple[str, list[Dict]]:
    """
    解析文本格式的八字标签，如：[bz sex=1]丁未 癸卯 癸亥 癸丑[/bz] 或 [bz sex=1]丁未 癸卯 癸亥 癸丑[/b
    
    Args:
        text: 包含八字标签的文本
        
    Returns:
        (清理后的文本, 八字案例列表)
    """
    if not text:
        return text, []
    
    bzcases = []
    parts = []
    last_pos = 0
    
    # 匹配 [bz sex=X]内容[/bz] 或 [bz sex=X]内容[/b 或 [bz sex=X]内容[ 或 [bz sex=X]内容（到段落末尾）
    # 支持 sex=1 或 sex="1" 格式
    # 使用分步匹配：先找到开始标签，再找到结束标记
    pattern = r'\[bz\s+sex=["\']?(\d+)["\']?\]'
    
    for match in re.finditer(pattern, text, re.IGNORECASE):
        start = match.start()
        tag_start_end = match.end()  # 标签开始位置之后
        
        # 添加标签前的文本
        if start > last_pos:
            parts.append(text[last_pos:start])
        
        # 提取性别
        sex_str = match.group(1)
        
        # 找到标签内容的结束位置：[/bz]、[/b、[ 或文本末尾
        remaining_text = text[tag_start_end:]
        # 查找结束标记（非贪婪）
        end_match = re.search(r'\[/bz\]|\[/b|\[', remaining_text)
        
        if end_match:
            # 找到了结尾标记
            bz_content = remaining_text[:end_match.start()].strip()
            tag_end = tag_start_end + end_match.end()
        else:
            # 没有找到结尾标记，说明标签到文本末尾
            bz_content = remaining_text.strip()
            tag_end = len(text)
        
        # 从八字内容中提取显示文本（去掉可能的空格，格式化为标准显示）
        bz_display = re.sub(r'\s+', ' ', bz_content).strip()
        # 生成标准八字格式（天干地支之间加空格）
        bz_text = ' '.join(re.findall(r'[甲乙丙丁戊己庚辛壬癸子丑寅卯辰巳午未申酉戌亥]+', bz_display))
        
        if bz_display:
            # 记录八字案例信息（位置稍后计算）
            bzcases.append({
                'type': 'bzcase',  # 标记类型：八字案例
                'display_text': bz_display,
                'bz': bz_text,
                'sex': int(sex_str) if sex_str and sex_str.isdigit() else (1 if sex_str else None),
                'in_original': True  # 暂时标记，后续会根据上下文调整
            })
            # 在文本中用八字显示文本替换标签
            parts.append(bz_display)
        
        last_pos = tag_end
        
        # 如果已经到了文本末尾，停止查找
        if tag_end >= len(text):
            break
    
    # 添加最后剩余的文本
    if last_pos < len(text):
        parts.append(text[last_pos:])
    
    # 构建清理后的文本
    cleaned_text = ''.join(parts)
    
    return cleaned_text, bzcases


def extract_html_to_json(html_file_path: str) -> List[Dict]:
    """
    提取HTML文件内容并转换为适合单表数据库的JSON格式
    
    Args:
        html_file_path: HTML文件路径
        
    Returns:
        扁平化的记录列表（每个段落一条记录）
    """
    html_file = Path(html_file_path)
    
    if not html_file.exists():
        return [{'error': f'文件不存在: {html_file_path}'}]
    
    try:
        with open(html_file, 'r', encoding='utf-8') as f:
            html_content = f.read()
        
        soup = BeautifulSoup(html_content, 'html.parser')
        
        # 提取基本信息
        title_tag = soup.find('title')
        raw_title = title_tag.get_text().strip() if title_tag else None
        
        # 格式化标题（去掉书籍前缀）
        title = format_title(raw_title) if raw_title else None
        
        # 从文件名提取信息
        filename = html_file.name
        filename_parts = filename.split('_')
        book_code = None
        book_name = None
        category = None
        page_info = None
        
        if len(filename_parts) >= 4:
            book_code = filename_parts[0]
            book_name = filename_parts[1]
            category = filename_parts[2]
            page_info = filename_parts[3] if len(filename_parts) > 3 else None
        
        # 提取页码
        page_num = extract_page_num(page_info) if page_info else None
        
        # 提取天干地支信息（从标题中）
        heaven_stem = None
        earth_branch = None
        
        if title:
            # 匹配天干（甲、乙、丙、丁、戊、己、庚、辛、壬、癸）
            stem_match = re.search(r'([甲乙丙丁戊己庚辛壬癸])', title)
            if stem_match:
                heaven_stem = stem_match.group(1)
            
            # 匹配地支（子、丑、寅、卯、辰、巳、午、未、申、酉、戌、亥）
            branch_match = re.search(r'([子丑寅卯辰巳午未申酉戌亥])', title)
            if branch_match:
                earth_branch = branch_match.group(1)
        
        # 提取所有段落
        content_items = soup.find_all('div', class_='content_item')
        paragraphs = []
        
        for idx, item in enumerate(content_items, 1):
            pid = item.get('pid', '')
            
            # 提取原文（保留八字案例位置信息）
            original_div = item.find('div', class_='content_o')
            original_text = ""
            original_bzcases = []  # 存储原文中的八字案例位置信息
            original_styles = []  # 存储原文中的样式标记位置信息（gold、bold等）
            if original_div:
                p_tags = original_div.find_all('p')
                if p_tags:
                    original_parts = []
                    
                    for p in p_tags:
                        # 提取段落文本并记录样式标记（gold、bold等）的位置
                        paragraph_text = ""
                        paragraph_styles = []  # 当前段落的样式标记
                        
                        # 遍历段落的所有直接子节点，构建文本并记录样式
                        for child in p.children:
                            if hasattr(child, 'name') and child.name:
                                # 是一个Tag
                                tag_name = child.name
                                
                                # 处理八字案例（bzcase）
                                if tag_name == 'span' and child.get('class') and 'bzcase' in child.get('class', []):
                                    # 八字案例会在下面单独处理，这里跳过
                                    pass
                                # 处理gold标签
                                elif tag_name == 'span' and child.get('class') and 'gold' in child.get('class', []):
                                    gold_text = child.get_text().strip()
                                    if gold_text:
                                        style_start = len(paragraph_text)
                                        paragraph_text += gold_text
                                        paragraph_styles.append({
                                            'type': 'gold',
                                            'text': gold_text,
                                            '_start_in_paragraph': style_start,
                                            '_length': len(gold_text),
                                            'in_original': True  # 标记在原文中
                                        })
                                # 处理b标签（bold）
                                elif tag_name == 'b':
                                    bold_text = child.get_text().strip()
                                    if bold_text:
                                        style_start = len(paragraph_text)
                                        paragraph_text += bold_text
                                        paragraph_styles.append({
                                            'type': 'bold',
                                            'text': bold_text,
                                            '_start_in_paragraph': style_start,
                                            '_length': len(bold_text),
                                            'in_original': True  # 标记在原文中
                                        })
                                # 其他标签，提取文本（但不记录样式）
                                else:
                                    text = child.get_text().strip()
                                    if text:
                                        paragraph_text += text
                            else:
                                # 是文本节点（NavigableString）
                                text = str(child).strip()
                                if text:
                                    paragraph_text += text
                        
                        # 如果没有通过遍历提取到文本，使用get_text()作为后备
                        if not paragraph_text:
                            paragraph_text = p.get_text().strip()
                        
                        # 检查段落中是否包含八字案例
                        bzcase_span = p.find('span', class_='bzcase')
                        
                        if bzcase_span:
                            # 提取八字案例信息
                            bz_text = bzcase_span.get('bz', '').strip()
                            sex_str = bzcase_span.get('sex', '').strip()
                            bz_display = bzcase_span.get_text().strip()  # 显示的八字文本（去掉图标等）
                            
                            # 获取八字案例前后的文本
                            # 方法：遍历段落的所有子节点，分别提取八字案例前后的文本
                            before_text = ""
                            after_text = ""
                            
                            # 获取八字案例前的所有文本
                            for sibling in bzcase_span.previous_siblings:
                                try:
                                    # 尝试获取Tag名称，如果是Tag则获取文本
                                    tag_name = getattr(sibling, 'name', None)
                                    if tag_name:
                                        # 是Tag，获取其文本（排除img标签）
                                        if tag_name != 'img':
                                            before_text = sibling.get_text().strip() + before_text
                                    else:
                                        # 是NavigableString或其他文本节点
                                        text = str(sibling).strip()
                                        if text:
                                            before_text = text + before_text
                                except Exception:
                                    # 如果出错，尝试直接转换为字符串
                                    try:
                                        text = str(sibling).strip()
                                        if text:
                                            before_text = text + before_text
                                    except Exception:
                                        pass
                            
                            # 获取八字案例后的所有文本
                            for sibling in bzcase_span.next_siblings:
                                try:
                                    # 尝试获取Tag名称，如果是Tag则获取文本
                                    tag_name = getattr(sibling, 'name', None)
                                    if tag_name:
                                        # 是Tag，获取其文本（排除img标签）
                                        if tag_name != 'img':
                                            after_text = after_text + sibling.get_text().strip()
                                    else:
                                        # 是NavigableString或其他文本节点
                                        text = str(sibling).strip()
                                        if text:
                                            after_text = after_text + text
                                except Exception:
                                    # 如果出错，尝试直接转换为字符串
                                    try:
                                        text = str(sibling).strip()
                                        if text:
                                            after_text = after_text + text
                                    except Exception:
                                        pass
                            
                            # 将同一个段落内的内容合并为一个字符串（避免不必要的换行）
                            # 注意：paragraph_text已经在上面处理了，这里需要重新构建包含八字案例的文本
                            combined_text = ""
                            if before_text:
                                combined_text += before_text
                            # 记录八字案例在段落中的位置
                            bzcase_start_in_paragraph = len(combined_text)
                            combined_text += bz_display
                            if after_text:
                                combined_text += after_text
                            
                            # 使用combined_text作为最终文本
                            final_text = combined_text
                            original_parts.append(final_text)
                            
                            # 如果段落中有样式标记，需要重新计算它们在combined_text中的位置
                            if paragraph_styles:
                                # 需要在combined_text中查找样式文本的位置
                                for style in paragraph_styles:
                                    style['_paragraph_index'] = len(original_parts) - 1
                                    # 在combined_text中查找样式文本的位置
                                    style_text = style['text']
                                    # 查找样式文本在combined_text中的位置
                                    pos = combined_text.find(style_text)
                                    if pos >= 0:
                                        style['_start_in_paragraph'] = pos
                                    else:
                                        # 如果找不到，尝试基于paragraph_text的位置调整
                                        # 这是后备方案，可能不够准确
                                        pass
                                original_styles.extend(paragraph_styles)
                                paragraph_styles = []
                            
                            # 记录八字案例信息（位置稍后计算，先记录在段落中的相对位置）
                            original_bzcases.append({
                                'type': 'bzcase',  # 标记类型：八字案例
                                'display_text': bz_display,
                                'bz': bz_text,
                                'sex': int(sex_str) if sex_str and sex_str.isdigit() else (1 if sex_str else None),
                                'in_original': True,  # 标记在原文中
                                '_paragraph_index': len(original_parts) - 1,  # 记录在哪个段落部分
                                '_start_in_paragraph': bzcase_start_in_paragraph  # 在段落中的相对位置
                            })
                        else:
                            # 普通段落，检查是否包含文本格式的八字标签
                            if paragraph_text:
                                # 解析文本格式的八字标签
                                cleaned_text, text_bzcases = parse_text_bzcase_tags(paragraph_text)
                                
                                # 如果找到文本格式的八字案例，添加到列表中
                                if text_bzcases:
                                    # 确保文本格式的八字案例也有type字段
                                    for bzcase in text_bzcases:
                                        if 'type' not in bzcase:
                                            bzcase['type'] = 'bzcase'
                                    original_bzcases.extend(text_bzcases)
                                    original_parts.append(cleaned_text)
                                    # 调整样式标记位置（因为文本可能被清理过）
                                    if paragraph_styles:
                                        # 文本格式的八字标签清理可能改变了文本长度，这里简化处理
                                        for style in paragraph_styles:
                                            style['_paragraph_index'] = len(original_parts) - 1
                                        original_styles.extend(paragraph_styles)
                                else:
                                    original_parts.append(paragraph_text)
                                    # 保存样式标记
                                    if paragraph_styles:
                                        for style in paragraph_styles:
                                            style['_paragraph_index'] = len(original_parts) - 1
                                        original_styles.extend(paragraph_styles)
                    
                    # 构建完整文本
                    original_text = '\n'.join(original_parts)
                    
                    # 现在基于完整文本计算八字案例的位置
                    # 先计算每个段落部分在全文中的起始位置
                    part_start_positions = []
                    current_pos = 0
                    for part in original_parts:
                        part_start_positions.append(current_pos)
                        current_pos += len(part)
                        # 如果不是最后一个部分，加上换行符的长度
                        if current_pos < len(original_text) and original_text[current_pos:current_pos+1] == '\n':
                            current_pos += 1
                    
                    # 合并八字案例和样式标记，统一计算位置
                    all_original_annotations = original_bzcases + original_styles
                    
                    # 根据记录的段落索引和段落内位置，计算在全文中的位置
                    for annotation in all_original_annotations:
                        if '_paragraph_index' in annotation and '_start_in_paragraph' in annotation:
                            part_idx = annotation['_paragraph_index']
                            if part_idx < len(part_start_positions):
                                # 在全文中的位置 = 段落部分在全文中的起始位置 + 标记在段落中的位置
                                annotation['start'] = part_start_positions[part_idx] + annotation['_start_in_paragraph']
                                
                                # 根据类型计算length
                                if annotation.get('type') == 'bzcase':
                                    annotation['length'] = len(annotation['display_text'])
                                else:
                                    annotation['length'] = annotation.get('_length', len(annotation.get('text', '')))
                            
                            # 删除临时字段
                            if '_paragraph_index' in annotation:
                                del annotation['_paragraph_index']
                            if '_start_in_paragraph' in annotation:
                                del annotation['_start_in_paragraph']
                            if '_length' in annotation:
                                del annotation['_length']
                else:
                    # 如果没有p标签，直接提取文本
                    original_text = original_div.get_text().strip()
            
            # 提取译文（保留八字案例位置信息）
            translated_div = item.find('div', class_='content_t')
            translated_text = ""
            translated_bzcases = []  # 存储译文中的八字案例位置信息
            translated_styles = []  # 存储译文中的样式标记位置信息（gold、bold等）
            if translated_div:
                p_tags = translated_div.find_all('p')
                if p_tags:
                    translated_parts = []
                    
                    for p in p_tags:
                        # 移除图标（但不移除八字案例中的图标，因为它会被get_text()处理）
                        non_bzcase_imgs = [img for img in p.find_all('img') if not img.find_parent('span', class_='bzcase')]
                        for img in non_bzcase_imgs:
                            img.decompose()
                        
                        # 提取段落文本并记录样式标记（gold、bold等）的位置
                        paragraph_text = ""
                        paragraph_styles = []  # 当前段落的样式标记
                        
                        # 遍历段落的所有直接子节点，构建文本并记录样式
                        for child in p.children:
                            if hasattr(child, 'name') and child.name:
                                # 是一个Tag
                                tag_name = child.name
                                
                                # 处理八字案例（bzcase）
                                if tag_name == 'span' and child.get('class') and 'bzcase' in child.get('class', []):
                                    # 八字案例会在下面单独处理，这里跳过
                                    pass
                                # 处理gold标签
                                elif tag_name == 'span' and child.get('class') and 'gold' in child.get('class', []):
                                    gold_text = child.get_text().strip()
                                    if gold_text:
                                        style_start = len(paragraph_text)
                                        paragraph_text += gold_text
                                        paragraph_styles.append({
                                            'type': 'gold',
                                            'text': gold_text,
                                            '_start_in_paragraph': style_start,
                                            '_length': len(gold_text),
                                            'in_original': False  # 标记在译文中
                                        })
                                # 处理b标签（bold）
                                elif tag_name == 'b':
                                    bold_text = child.get_text().strip()
                                    if bold_text:
                                        style_start = len(paragraph_text)
                                        paragraph_text += bold_text
                                        paragraph_styles.append({
                                            'type': 'bold',
                                            'text': bold_text,
                                            '_start_in_paragraph': style_start,
                                            '_length': len(bold_text),
                                            'in_original': False  # 标记在译文中
                                        })
                                # 其他标签，提取文本（但不记录样式）
                                else:
                                    text = child.get_text().strip()
                                    if text:
                                        paragraph_text += text
                            else:
                                # 是文本节点（NavigableString）
                                text = str(child).strip()
                                if text:
                                    paragraph_text += text
                        
                        # 如果没有通过遍历提取到文本，使用get_text()作为后备
                        if not paragraph_text:
                            paragraph_text = p.get_text().strip()
                        
                        # 检查段落中是否包含八字案例
                        bzcase_span = p.find('span', class_='bzcase')
                        
                        if bzcase_span:
                            # 提取八字案例信息
                            bz_text = bzcase_span.get('bz', '').strip()
                            sex_str = bzcase_span.get('sex', '').strip()
                            bz_display = bzcase_span.get_text().strip()  # 显示的八字文本（去掉图标等）
                            
                            # 获取八字案例前后的文本
                            # 方法：遍历段落的所有子节点，分别提取八字案例前后的文本
                            before_text = ""
                            after_text = ""
                            
                            # 获取八字案例前的所有文本
                            for sibling in bzcase_span.previous_siblings:
                                try:
                                    # 尝试获取Tag名称，如果是Tag则获取文本
                                    tag_name = getattr(sibling, 'name', None)
                                    if tag_name:
                                        # 是Tag，获取其文本（排除img标签）
                                        if tag_name != 'img':
                                            before_text = sibling.get_text().strip() + before_text
                                    else:
                                        # 是NavigableString或其他文本节点
                                        text = str(sibling).strip()
                                        if text:
                                            before_text = text + before_text
                                except Exception:
                                    # 如果出错，尝试直接转换为字符串
                                    try:
                                        text = str(sibling).strip()
                                        if text:
                                            before_text = text + before_text
                                    except Exception:
                                        pass
                            
                            # 获取八字案例后的所有文本
                            for sibling in bzcase_span.next_siblings:
                                try:
                                    # 尝试获取Tag名称，如果是Tag则获取文本
                                    tag_name = getattr(sibling, 'name', None)
                                    if tag_name:
                                        # 是Tag，获取其文本（排除img标签）
                                        if tag_name != 'img':
                                            after_text = after_text + sibling.get_text().strip()
                                    else:
                                        # 是NavigableString或其他文本节点
                                        text = str(sibling).strip()
                                        if text:
                                            after_text = after_text + text
                                except Exception:
                                    # 如果出错，尝试直接转换为字符串
                                    try:
                                        text = str(sibling).strip()
                                        if text:
                                            after_text = after_text + text
                                    except Exception:
                                        pass
                            
                            # 将同一个段落内的内容合并为一个字符串（避免不必要的换行）
                            # 注意：paragraph_text已经在上面处理了，这里需要重新构建包含八字案例的文本
                            combined_text = ""
                            if before_text and before_text != '*':
                                combined_text += before_text
                            # 记录八字案例在段落中的位置
                            bzcase_start_in_paragraph = len(combined_text)
                            combined_text += bz_display
                            if after_text and after_text != '*':
                                combined_text += after_text
                            
                            # 使用combined_text作为最终文本
                            final_text = combined_text
                            translated_parts.append(final_text)
                            
                            # 如果段落中有样式标记，需要重新计算它们在combined_text中的位置
                            if paragraph_styles:
                                # 需要在combined_text中查找样式文本的位置
                                for style in paragraph_styles:
                                    style['_paragraph_index'] = len(translated_parts) - 1
                                    # 在combined_text中查找样式文本的位置
                                    style_text = style['text']
                                    # 查找样式文本在combined_text中的位置
                                    pos = combined_text.find(style_text)
                                    if pos >= 0:
                                        style['_start_in_paragraph'] = pos
                                    else:
                                        # 如果找不到，尝试基于paragraph_text的位置调整
                                        # 这是后备方案，可能不够准确
                                        pass
                                translated_styles.extend(paragraph_styles)
                                paragraph_styles = []
                            
                            # 记录八字案例信息（位置稍后计算，先记录在段落中的相对位置）
                            translated_bzcases.append({
                                'type': 'bzcase',  # 标记类型：八字案例
                                'display_text': bz_display,
                                'bz': bz_text,
                                'sex': int(sex_str) if sex_str and sex_str.isdigit() else (1 if sex_str else None),
                                'in_original': False,  # 标记在译文中
                                '_paragraph_index': len(translated_parts) - 1,  # 记录在哪个段落部分
                                '_start_in_paragraph': bzcase_start_in_paragraph  # 在段落中的相对位置
                            })
                        else:
                            # 普通段落，检查是否包含文本格式的八字标签
                            if paragraph_text and paragraph_text != '*':
                                # 解析文本格式的八字标签
                                cleaned_text, text_bzcases = parse_text_bzcase_tags(paragraph_text)
                                
                                # 如果找到文本格式的八字案例，添加到列表中
                                if text_bzcases:
                                    # 标记这些八字案例在译文中，并确保有type字段
                                    for bzcase in text_bzcases:
                                        bzcase['in_original'] = False
                                        if 'type' not in bzcase:
                                            bzcase['type'] = 'bzcase'
                                    translated_bzcases.extend(text_bzcases)
                                    translated_parts.append(cleaned_text)
                                    # 调整样式标记位置（因为文本可能被清理过）
                                    if paragraph_styles:
                                        # 文本格式的八字标签清理可能改变了文本长度，这里简化处理
                                        for style in paragraph_styles:
                                            style['_paragraph_index'] = len(translated_parts) - 1
                                        translated_styles.extend(paragraph_styles)
                                else:
                                    translated_parts.append(paragraph_text)
                                    # 保存样式标记
                                    if paragraph_styles:
                                        for style in paragraph_styles:
                                            style['_paragraph_index'] = len(translated_parts) - 1
                                        translated_styles.extend(paragraph_styles)
                    
                    # 构建完整文本
                    translated_text = '\n'.join(translated_parts)
                    
                    # 现在基于完整文本计算八字案例的位置
                    # 先计算每个段落部分在全文中的起始位置
                    part_start_positions = []
                    current_pos = 0
                    for part in translated_parts:
                        part_start_positions.append(current_pos)
                        current_pos += len(part)
                        # 如果不是最后一个部分，加上换行符的长度
                        if current_pos < len(translated_text) and translated_text[current_pos:current_pos+1] == '\n':
                            current_pos += 1
                    
                    # 合并八字案例和样式标记，统一计算位置
                    all_translated_annotations = translated_bzcases + translated_styles
                    
                    # 根据记录的段落索引和段落内位置，计算在全文中的位置
                    for annotation in all_translated_annotations:
                        if '_paragraph_index' in annotation and '_start_in_paragraph' in annotation:
                            part_idx = annotation['_paragraph_index']
                            if part_idx < len(part_start_positions):
                                # 在全文中的位置 = 段落部分在全文中的起始位置 + 标记在段落中的位置
                                annotation['start'] = part_start_positions[part_idx] + annotation['_start_in_paragraph']
                                
                                # 根据类型计算length
                                if annotation.get('type') == 'bzcase':
                                    annotation['length'] = len(annotation['display_text'])
                                else:
                                    annotation['length'] = annotation.get('_length', len(annotation.get('text', '')))
                            
                            # 删除临时字段
                            if '_paragraph_index' in annotation:
                                del annotation['_paragraph_index']
                            if '_start_in_paragraph' in annotation:
                                del annotation['_start_in_paragraph']
                            if '_length' in annotation:
                                del annotation['_length']
            
            # 合并原文和译文中的所有标记（八字案例、样式等）
            # 注意：上面已经计算了位置，所以这里直接合并即可
            all_annotations = all_original_annotations + all_translated_annotations
            
            # 生成章节唯一ID
            page_id = None
            if book_code and category and page_num is not None:
                page_id = f"{book_code}_{category}_p{page_num:03d}"
            
            # 扁平化：每个段落一条记录
            paragraphs.append({
                'book_code': book_code,  # 书籍代码
                'book_name': book_name,  # 书籍名称
                'category': category,  # 大章节
                'page_num': page_num,  # 小章节（页码）
                'page_id': page_id,  # 章节唯一ID（用于快速查询某章节的所有段落）
                'title': title,  # 标题
                'heaven_stem': heaven_stem,  # 天干
                'earth_branch': earth_branch,  # 地支
                'title_keywords': extract_title_keywords(title) if title else None,  # 标题关键词
                'pid': pid,  # 段落唯一ID（可拆分验证数据来源）
                'order_num': idx,  # 段落顺序
                'original_text': original_text,  # 原文
                'translated_text': translated_text,  # 译文
                'annotations': all_annotations if all_annotations else None
                # 所有标记位置信息数组（包含八字案例、样式等），用于前端定位和样式
                # annotations结构：
                # - type: 'bzcase' | 'gold' | 'bold' 等类型
                # - start: 在文本中的起始位置
                # - length: 文本长度
                # - in_original: true/false 是否在原文中
                # - 对于bzcase类型，还有: display_text, bz, sex
                # - 对于样式类型，还有: text
            })
        
        # 返回扁平化的记录列表（每个段落一条记录）
        return paragraphs
    
    except Exception as e:
        return [{
            'error': str(e),
            'filepath': str(html_file)
        }]


def save_records_to_json(records: List[Dict], output_json_path: str):
    """
    将记录列表保存为JSON文件
    
    Args:
        records: 记录列表
        output_json_path: 输出JSON文件路径
    """
    if not records:
        print("⚠️  没有记录需要保存")
        return
    
    output_file = Path(output_json_path)
    output_file.parent.mkdir(parents=True, exist_ok=True)
    
    # 过滤掉错误记录
    valid_records = [r for r in records if 'error' not in r]
    
    # 写入JSON文件
    with open(output_file, 'w', encoding='utf-8') as f:
        json.dump(valid_records, f, ensure_ascii=False, indent=2)
    
    print(f"✅ JSON文件已保存: {output_json_path} ({len(valid_records)} 条记录)")




def main():
    """主函数"""
    if len(sys.argv) < 2:
        script_name = Path(__file__).name
        print("用法:")
        print(f"  单个文件: python {script_name} <html_file_path> [output_json_path]")
        print(f"  批量处理: python {script_name} <html_directory_path> [output_json_path]")
        print("示例:")
        print(f"  python {script_name} ../downloads/b02_滴天髓/b02_滴天髓_i01_p001_滴天髓1-论甲日干.html")
        print(f"  python {script_name} ../downloads/b02_滴天髓 output.json")
        sys.exit(1)
    
    input_path = Path(sys.argv[1])
    output_path = sys.argv[2] if len(sys.argv) > 2 else None
    
    all_records = []
    
    # 判断是文件还是目录
    if input_path.is_file():
        # 单个文件处理
        print(f"📄 正在提取: {input_path}")
        records = extract_html_to_json(str(input_path))
        
        if records and 'error' in records[0]:
            print(f"❌ 错误: {records[0]['error']}")
            sys.exit(1)
        
        print(f"✅ 提取了 {len(records)} 条记录")
        all_records = records
        
        # 输出JSON（默认保存到脚本所在目录）
        if output_path:
            # 如果是相对路径，则相对于脚本所在目录
            if not Path(output_path).is_absolute():
                output_path = str(Path(__file__).parent / output_path)
            save_records_to_json(all_records, output_path)
        else:
            # 默认输出到脚本所在目录（data-analysis目录）
            script_dir = Path(__file__).parent
            default_output = script_dir / f"{input_path.stem}.json"
            save_records_to_json(all_records, str(default_output))
    
    elif input_path.is_dir():
        # 批量处理目录下的所有HTML文件（支持递归搜索）
        print(f"📁 批量处理目录: {input_path}")
        
        # 递归查找所有HTML文件
        html_files = sorted(list(input_path.rglob("*.html")))
        
        if not html_files:
            print(f"❌ 在目录中未找到 HTML 文件")
            sys.exit(1)
        
        print(f"📋 找到 {len(html_files)} 个 HTML 文件")
        
        # 统计进度
        total_files = len(html_files)
        success_count = 0
        error_count = 0
        
        # 处理所有文件
        for idx, html_file in enumerate(html_files, 1):
            print(f"  [{idx}/{total_files}] 处理: {html_file.relative_to(input_path)}")
            records = extract_html_to_json(str(html_file))
            
            if records and 'error' not in records[0]:
                all_records.extend(records)
                success_count += 1
                print(f"    ✅ {len(records)} 条记录")
            else:
                error_count += 1
                error_msg = records[0].get('error', '未知错误') if records else '未知错误'
                print(f"    ❌ 错误: {error_msg}")
        
        print(f"\n✅ 处理完成:")
        print(f"  成功处理: {success_count} 个文件")
        print(f"  处理失败: {error_count} 个文件")
        print(f"  总共提取: {len(all_records)} 条记录")
        
        # 输出JSON（默认保存到脚本所在目录）
        if output_path:
            # 如果是相对路径，则相对于脚本所在目录
            if not Path(output_path).is_absolute():
                output_path = str(Path(__file__).parent / output_path)
            save_records_to_json(all_records, output_path)
        else:
            # 默认输出到脚本所在目录（data-analysis目录）
            script_dir = Path(__file__).parent
            default_output = script_dir / f"{input_path.name}.json"
            save_records_to_json(all_records, str(default_output))
    else:
        print(f"❌ 路径不存在: {input_path}")
        sys.exit(1)


if __name__ == '__main__':
    main()

